﻿using System;

namespace Zive.Color {

    public static class LabColorConverter {

        private const double κ          = 24389d / 27d;
        private const double ε          = 216d / 24389d;
        private const double cuberoot_ε = 0.20689655172413793103448275862069; //Math.Pow( ε, 1d / 3d );

        //
        // Convert XYZ to L*a*b*
        //

        private static double f( double q ) {
            if ( q > ε ) {
                return Math.Pow( q, 1d / 3d );
            } else {
                return ( κ * q + 16d ) / 116d;
            }
        }

        public static LabColor Convert( XyzColor inputColor ) {
            double[ , ] reference = inputColor.Illuminant.GetReferenceWhite( );

            double fx = f( inputColor.X / reference[ 0, 0 ] );
            double fy = f( inputColor.Y / reference[ 1, 0 ] );
            double fz = f( inputColor.Z / reference[ 2, 0 ] );

            double L = ( 116d * fy ) - 16d;
            double a = 500d * ( fx - fy );
            double b = 200d * ( fy - fz );
            return new LabColor( inputColor.Illuminant, L, a, b );
        }

        public static LabColor Convert( XyzColor inputColor, Illuminants newIlluminant ) {
            var tempColor = inputColor.Clone( );
            if ( newIlluminant != tempColor.Illuminant ) {
                var transform = new BradfordTransform( tempColor.Illuminant, newIlluminant );
                tempColor = transform.Transform( tempColor );
            }
            return Convert( tempColor );
        }

        //
        // Convert L*a*b* to XYZ
        //

        private static double inv_f_xz( double q ) {
            if ( q > cuberoot_ε ) {
                return Math.Pow( q, 3d );
            } else {
                return ( 116d * q - 16d ) / κ;
            }
        }

        private static double inv_f_y( double q ) {
            if ( q > κ * ε ) {
                return Math.Pow( ( q + 16d ) / 116d, 3 );
            } else {
                return q / κ;
            }
        }

        public static XyzColor Convert( LabColor inputColor ) {
            double[,] reference = inputColor.Illuminant.GetReferenceWhite( );

            double fy = ( inputColor.Lstar + 16d ) / 116d;
            double xr = inv_f_xz( inputColor.Astar / 500d + fy );
            double yr = inv_f_y( inputColor.Lstar );
            double zr = inv_f_xz( fy - inputColor.Bstar / 200d );

            double X = xr * reference[0, 0];
            double Y = yr * reference[1, 0];
            double Z = zr * reference[2, 0];
            return new XyzColor( inputColor.Illuminant, X, Y, Z );
        }

        public static XyzColor Convert( LabColor inputColor, Illuminants newIlluminant ) {
            var color = Convert( inputColor );
            if ( inputColor.Illuminant == newIlluminant ) {
                // no transform needed
                return color;
            }
            var transform = BradfordTransformFactory.Create( inputColor.Illuminant, newIlluminant );
            return transform.Transform( color );
        }

    }

}
